package si.unimb.src.wifisms.control;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

import android.content.Context;
import android.util.Log;
import si.unimb.src.wifisms.control.threads.*;
import si.unimb.src.wifisms.entity.*;

/**
 * 
 * @author Ales
 * 
 * Posiljanje SMSov v batch nacinu.
 * Razred preverja veljavnost AuthToken-ov in posilja SMSe.
 * 
 * Vsaka operacija poteka v svoji niti, le te pa se izvajajo paralelno.
 * Mozna je tudi prijava na dogodke (onStart, onUpdate, onFinish).
 *
 */
public class BatchSender {

	// Objekti, nad katerimi se vse dogaja
	protected ArrayList<SMSMessage> messages;
	protected ArrayList<AuthToken> authTokens = new ArrayList<AuthToken>();
	protected HashMap<AbstractRunnable, Thread> threads = new HashMap<AbstractRunnable, Thread>();
	
	// Counterji za napredek
	protected AtomicInteger progressMessages   = new AtomicInteger();
	protected AtomicInteger progressAuthTokens = new AtomicInteger();
	
	// Poslusalci, ki so se prijavili na dogodke
	protected ArrayList<BatchSenderListener> listeners = new ArrayList<BatchSenderListener>();
	
	// Context
	protected Context context;
	
	/**
	 * 
	 * Privzeti konstruktor
	 * 
	 */
	public BatchSender(Context context) {
	
		this.context = context;
		clearMessages();
		
	}
	
	/**
	 * 
	 * Metoda za dodajanje posameznih SMS sporocil.
	 * 
	 * @param message SMS sporocilo, ki ga zelimo dodati.
	 */
	public void addMessage(SMSMessage message) {
	
		messages.add(message);
		
	}
	
	/**
	 * 
	 * Metoda za dodajanje vec SMS sporocil.
	 * S tem tudi povozimo vsa prej dodana sporocila.
	 * 
	 * @param messages Seznam SMS sporocil
	 */
	public void setMessages(ArrayList<SMSMessage> messages) {
	
		this.messages = messages;
		
	}
	
	/**
	 * 
	 * Brisanje vseh dodanih SMS sporocil.
	 * 
	 */
	public void clearMessages() {
		
		messages = new ArrayList<SMSMessage>();
		
	}
	
	/**
	 * 
	 * Posljemo vsa dodana sporocila
	 * 
	 * @throws InvalidTokenException V primeru da je en account brez gesla.
	 */
	public void sendAll() throws InvalidTokenException {
		
		Log.d("HelloWS2", "Calling sendAll");
		
		// Administracija
		onStart();
		
		// Ustvarimo AuthTokene
		Log.d("HelloWS2", "creating auth tokens...");
		createAuthTokens();
		
		// Izpraznimo trenutne niti
		threads.clear();
		
		// Ustvarimo niti za preverjanje veljavnosti
		AbstractRunnable tmpRunnable;
		for(AuthToken authToken : authTokens) {
		
			// ne naredimo niti ce ni potrebno tokena preveriti
			if(authToken.needsValidation()) {
			
				authToken.setStatus(AuthToken.STATUS_NOT_CHECKED);
				tmpRunnable = new TokenValidatorRunnable(this, authToken);
				threads.put(tmpRunnable, new Thread(tmpRunnable));
				
			}
			
		}
		Log.d("HelloWS2", threads.size() + " token(s) to validate");
		
		// Ustvarimo niti za posiljanje sporocil
		Log.d("HelloWS2", messages.size() + " messages(s) to send");
		for(SMSMessage message : messages) {
		
			tmpRunnable = new SMSSenderRunnable(this, message);
			threads.put(tmpRunnable, new Thread(tmpRunnable));
			
		}
		
		// Zaganjanje niti
		Log.d("HelloWS2", "running " + threads.size() + " thread(s)...");
		for(Thread t : threads.values()) {
			
			t.start();
			
		}
		
	}
	
	/**
	 * 
	 * Ta metoda se uporablja, da posamezna nit ob koncu sporoci BatchSenderju
	 * da je koncala svojo nalogo. Pri tem se nit odstrani iz threads HashMap-a.
	 * 
	 * @param runnable Instanca AbstractRunnable, ki je koncal s svojo nalogo
	 */
	public void runnableFinished(AbstractRunnable runnable) {
	
		// Ali obstaja ta nit?
		if(threads.remove(runnable) != null) {
			
			// Posodobimo ustrezen stevec
			if(runnable.getClass() == SMSSenderRunnable.class) {
			
				progressMessages.incrementAndGet();
				
			} else if(runnable.getClass() == TokenValidatorRunnable.class) {
				
				progressAuthTokens.incrementAndGet();
				
			}
			
			Log.d("HelloWS2", "runnableFinished: " + runnable.getClass().getSimpleName() + " (" + runnable.getDuration() + " ms)");
			
			// Administracijske funkcije
			updateStatus();
			
			if(threads.isEmpty()) {
			
				onFinish();
				
			}
			
		}
		
	}
	
	/**
	 * 
	 * Ob zacetku posiljanja
	 * 
	 */
	protected void onStart() {
		
		for(BatchSenderListener listener : listeners) {
		
			listener.onStart();
			
		}
		
	}
	
	/**
	 * 
	 * Posodobi status (naprimer GUI vmesnik)
	 * 
	 */
	protected void updateStatus() {
		
		for(BatchSenderListener listener : listeners) {
			
			listener.onUpdate();
			
		}
		
	}
	
	/**
	 * 
	 * Ob koncu posiljanja
	 * 
	 */
	protected void onFinish() {
		
		for(BatchSenderListener listener : listeners) {
			
			listener.onFinish();
			
		}
		
	}
	
	/**
	 * 
	 * Prijava na dogodke
	 * 
	 * @param listener
	 */
	public void addListener(BatchSenderListener listener) {
		
		listeners.add(listener);
		
	}
	
	/**
	 * 
	 * Odjava iz dogodkov
	 * 
	 * @param listener
	 */
	public void removeListener(BatchSenderListener listener) {
		
		listeners.remove(listener);
		
	}
	
	/**
	 * 
	 * Vrne true ce je aktivna se vsaj ena nit
	 * 
	 * @return true, ce je aktivna se vsaj ena nit
	 */
	public boolean isActive() {
		
		for(Thread t : threads.values()) {
		
			if(t.isAlive()) return true;
			
		}
		
		return false;
		
	}
	
	/**
	 * 
	 * Ustvari tokene za sporocila, ki se zelijo poslati
	 * 
	 * @throws InvalidTokenException vrze ce kateri account nima vpisanega gesla
	 */
	private void createAuthTokens() throws InvalidTokenException {
		
		for(SMSMessage message : messages) {
		
			// Generiranje
			AuthToken token = message.createAuthToken();
			
			// V ArrayList se dodajo samo se neobstojeci
			if(!authTokens.contains(token)) {
			
				authTokens.add(token);
				
			}
			
		}
		
	}

	/**
	 * 
	 * Getter za Context
	 * 
	 * @return Context
	 */
	public Context getContext() {
	
		return context;
		
	}
	
}
